from .analysis import CommitQuery
from .database import CVEDatabase
from subprocess import Popen, PIPE, STDOUT
from tqdm import tqdm
import os
import stat
import re
import requests

class CVETracker:
    def __init__(self, cve_data_db: CVEDatabase):
        # 初始化组件
        self.commit_query = CommitQuery()
        self.cve_data_db = cve_data_db
        self.commit_url = "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id="

    def _check_mainline_commit(self, commit_id: str):
        try:
            # 定义shell脚本的路径            
            script_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "scripts")
            shell_script_path = os.path.join(script_dir, "check_mainline_commit.sh")

            st = os.stat(shell_script_path)
            if not st.st_mode & stat.S_IEXEC:
            # 如果脚本不可执行，则添加执行权限
                os.chmod(shell_script_path, st.st_mode | stat.S_IEXEC)
            
            # 执行shell脚本，并传递commit_id作为参数
            output = []
            command = f"bash {shell_script_path} {commit_id}"
            print(f"执行脚本：{command}")
            process = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
            with process.stdout:
                for line in iter(process.stdout.readline, b''):
                    out = line.decode().strip()
                    print(out)
                    output.append(out)
            # 捕获并处理标准输出和标准错误
            result = output[-1]
            return result
        except Exception as e:
            print(f"An error occurred while executing the shell script: {e}")
            return str(e)
        
    def _check_mainline_commit_url(self, commit_id: str):
        try:
            url = self.commit_url + commit_id
            # 发送 HTTP 请求（添加浏览器头避免被拦截）
            headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'}
            response = requests.get(url, headers=headers, timeout=10)
            response.raise_for_status()
            
            # 匹配图片中的两种 commit 格式
            patterns = [
                # 匹配格式：commit e563b01208f4d1f609bcb23... upstream
                r'commit\s+([0-9a-f]{12,40})\b.*?\bupstream',
                # 匹配格式：Upstream commit 7828e9363ac4d23b02419fbf
                r'[uU]pstream\s+commit\s+([0-9a-f]{12,40})\b'
            ]
            
            # 在页面内容中搜索匹配项
            content = response.text
            mainline_commit = None
            for pattern in patterns:
                match = re.search(pattern, content, re.DOTALL)
                if match:
                    mainline_commit = match.group(1)
                    # 取第一个有效匹配后退出循环
                    if len(mainline_commit) >= 12:  
                        break
            
            # 返回结果逻辑
            if mainline_commit:
                # 确保返回完整 40 位或至少 12 位 hash
                return mainline_commit[:40] if len(mainline_commit) > 40 else mainline_commit
            else:
                return None

        except requests.exceptions.RequestException as e:
            return f"HTTP error: {str(e)}"
        except Exception as e:
            return f"Error: {str(e)}"
        
    def _check_commit(self, os_version: str, commit_id: str):
        try:
            # 定义shell脚本的路径            
            script_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "scripts")
            shell_script_path = os.path.join(script_dir, "check_commit.sh")

            st = os.stat(shell_script_path)
            if not st.st_mode & stat.S_IEXEC:
            # 如果脚本不可执行，则添加执行权限
                os.chmod(shell_script_path, st.st_mode | stat.S_IEXEC)
            
            # 执行shell脚本，并传递commit_id作为参数
            command = f"bash {shell_script_path} {os_version} {commit_id}"
            print(f"执行脚本：{command}")
            process = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
            with process.stdout:
                for line in iter(process.stdout.readline, b''):
                    print(line.decode().strip())
            return_code = process.wait()
            print("return_code: ", return_code)
            return return_code
        except Exception as e:
            print(f"An error occurred while executing the shell script: {e}")
            return None, str(e), -1

    def update_cve_commit(self, cve: str):
        """检查单个CVE在每一个操作系统版本中是否引入, 是否修复"""
        # 获取commit信息
        commits_info = self.commit_query.get_commit_infos(cve)
        if len(commits_info) == 0:
            print(f'Warning! Current CVE: {cve} has no commit info...')
            return False

        # 引入commit查询，只查询mainline版本
        intro_commits_info: dict = self.commit_query.get_introduced_commit(commits_info)
        intro_commit_mainline = None
        if intro_commits_info:
            intro_commit_0 = list(intro_commits_info.values())[0]
            intro_commit_mainline = self._check_mainline_commit(intro_commit_0)
            if not intro_commit_mainline:
                intro_commit_mainline = intro_commit_0
        print(f"引入commit: {intro_commit_mainline}")

        # 修补commit查询，这里查询mainline版本
        fixed_commits_info: dict = self.commit_query.get_fixed_commit(commits_info)
        fixed_commit_mainline = None
        if fixed_commits_info:
            fixed_commit_0 = list(fixed_commits_info.values())[0]
            fixed_commit_mainline = self._check_mainline_commit(fixed_commit_0)
            if not fixed_commit_mainline:
                fixed_commit_mainline = fixed_commit_0
        print(f"修复commit: {fixed_commit_mainline}")

        # 创建cve, 同时填入引入commit和修补commit版本
        self.cve_data_db.add_cve(cve, intro_commit_mainline, fixed_commit_mainline)

        # stable版本修补commit查询
        for fixed_version, fixed_commit in fixed_commits_info.items():
            stable_fixed_version = self.cve_data_db.check_os_version_is_stable_version(fixed_version)
            if stable_fixed_version:
                print(f"修复commit: {fixed_commit} in {stable_fixed_version}")
                self.cve_data_db.add_cve_commit(cve, stable_fixed_version, fixed_commit)
        
        # openEuler版本修补commit查询
        openEuler_os_versions = self.cve_data_db.get_os_versions(type="openEuler")
        for os_version in openEuler_os_versions:
            parent_os_version = self.cve_data_db.get_os_parents(os_version)
            stable_commit = self.cve_data_db.check_fixed_commit_id(cve, parent_os_version)
            if stable_commit:
                print(f"修复commit: {stable_commit} in {os_version}")
                self.cve_data_db.add_cve_commit(cve, os_version, stable_commit)

    def check_immediately(self):
        """立刻更新数据库"""
        cve_list = self.commit_query.get_all_cve()
        for cve in tqdm(cve_list):
            try:
                self.update_cve_commit(cve)
            except Exception as e:
                print(f"Error processing {cve}: {str(e)}")

    def query_cve(self, cve_name: str, os_version: str, intro_commit: str, fixed_commit: str) -> tuple[int]:
        """
        查询CVE状态

        return: CVE的状态, 返回值为tuple, 包含两个int, 分别表示引入和修补的状态
        引入状态: 0 - 查询不到引入commit   1 - 受影响   2 - 不受影响  3 - 其他错误
        修复状态: 0 - 查询不到修补commit   1 - 已修复   2 - 未修复    3 - 其他错误
        """
        intro_code = 0
        fixed_code = 0

        if not intro_commit:
            intro_code = 0
        else:
            return_code = self._check_commit(os_version, intro_commit)
            print("return_code: ", return_code)
            if return_code == 0:    # commit合入了分支
                intro_code = 1      # 受影响
            elif return_code == 1:  # commit没有合入分支中
                intro_code = 2      # 不受影响
            else:
                intro_code = 3

        if not fixed_commit:
            fixed_code = 0
        else:
            return_code = self._check_commit(os_version, fixed_commit)
            print("return_code: ", return_code)
            if return_code == 0:    # commit合入了分支
                fixed_code = 1      # 已修复
            elif return_code == 1:  # commit没有合入分支中
                fixed_code = 2      # 未修复
            else:
                fixed_code = 3
        return (intro_code, fixed_code)
    
    def query_all_os_cve(self, cve_name: str) -> tuple:
        code_dict = {}
        intro_commit = self.cve_data_db.check_intro_commit_id_mainline(cve_name)
        fixed_commit = self.cve_data_db.check_fixed_commit_id_mainline(cve_name)
        print(f"引入commit: {intro_commit}, 修复commit: {fixed_commit}")

        for os_version in self.cve_data_db.get_os_versions(type='stable'):
            print(f"检查{cve_name}是否被引入了{os_version}中")
            code = self.query_cve(cve_name, os_version, intro_commit, fixed_commit)
            code_dict[os_version] = code
        result = {}
        for os_version in self.cve_data_db.get_os_versions(type='openEuler'):
            parent_os = self.cve_data_db.get_os_parents(os_version)
            result[os_version] = code_dict[parent_os]
        return result, intro_commit, fixed_commit
    
    def fix_cve(self, cve_name: str, os_version: str, user_name: str, user_email: str, number: str):
        """
        修补漏洞 (创建pr)

        return: 修复的状态, 返回值为int
            0: 推送成功，创建pr
            1: 已经修复，无需重新修复
            2: 没有修复补丁
            3: 无法修复，存在冲突
            4: 网络异常，请重新尝试
        """
        fixed_commit = self.cve_data_db.check_fixed_commit_id_mainline(cve_name)
        print(f"查询到修复commit: {fixed_commit}")
        if not fixed_commit:
            return 2, None
        else:
            return_code = self._check_commit(os_version, fixed_commit)
            if return_code == 0:
                return 1, None
        
        # 执行PR创建脚本
        script_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "scripts")
        shell_script_path = os.path.join(script_dir, "push_patch.sh")
        st = os.stat(shell_script_path)
        if not st.st_mode & stat.S_IEXEC:
        # 如果脚本不可执行，则添加执行权限
            os.chmod(shell_script_path, st.st_mode | stat.S_IEXEC)
        try:
            output = []
            command = f"bash {shell_script_path} {fixed_commit} {os_version} {user_name} {user_email} {cve_name} {number} 'mainline'"
            print(f"执行脚本：{command}")
            process = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
            with process.stdout:
                for line in iter(process.stdout.readline, b''):
                    out = line.decode().strip()
                    print(out)
                    output.append(out)
            return_code = process.wait()
            branch = output[-1]
            if return_code == 0:
                return 0, branch
            elif return_code == 2:
                return 3, None
            else:
                return 4, None
        except FileNotFoundError:
            print(f"错误：找不到patch推送脚本 {shell_script_path}")
            return 3, None
        except Exception as e:
            print(f"未知错误：{str(e)}")
            return 3, None
    
    def get_analyze_result(self, code: tuple[int]) -> str:
        """
        将return_code转化为str
        code: return code
        mode: 仅有analyze create_pr两种模式
        """
        result = ""
        if code[0] == 0:
            result += "查询不到引入commit，"
        elif code[0] == 1:
            result += "受影响，"
        elif code[0] == 2:
            result += "无影响，"
        if code[1] == 0:
            result += "查询不到修补commit。"
        elif code[1] == 1:
            result += "已修复。"
        elif code[1] == 2:
            result += "未修复。"
        
        if code[0] == 3 or code[1] == 3:
            result = "未知错误，请检查日志"
        print(result)
        return result

    def get_fixed_result(self, code: int) -> str:
        if code == 0:
            return "pr创建成功，"
        elif code == 1:
            return "已经修复，无需重复修复。"
        elif code == 2:
            return "没有修复补丁。"
        elif code == 3:
            return "无法修复，存在冲突。"
        elif code == 4:
            return "网络异常，请重新尝试。"

